<template>
  <div class="page">
    <pw-section class="blue" label="Request" ref="request">
      <ul>
        <li>
          <label for="method">Method</label>
          <select id="method" v-model="method">
            <option>GET</option>
            <option>POST</option>
            <option>PUT</option>
            <option>DELETE</option>
            <option>OPTIONS</option>
          </select>
        </li>
        <li>
          <label for="url">URL</label>
          <input id="url" type="url" v-bind:class="{ error: !isValidURL }" v-model="url" v-on:keyup.enter="sendRequest">
        </li>
        <li>
          <label for="path">Path</label>
          <input id="path" v-model="path" v-on:keyup.enter="sendRequest">
        </li>
        <li>
          <label for="action">&nbsp;</label>
          <button id="action" name="action" @click="sendRequest" :disabled="!isValidURL">Send</button>
        </li>
      </ul>
    </pw-section>
    <pw-section class="blue-dark" label="Request Body" v-if="method === 'POST' || method === 'PUT'">
      <ul>
        <li>
          <label>Content Type</label>
          <select v-model="contentType">
            <option>application/json</option>
            <option>www-form/urlencoded</option>
          </select>
          <span>
            <input v-model="rawInput" style="cursor: pointer;" type="checkbox" id="rawInput">
            <label for="rawInput" style="cursor: pointer;">Raw Input</label>
          </span>
        </li>
      </ul>
      <div v-if="!rawInput">
        <ol v-for="(param, index) in bodyParams">
          <li>
            <label :for="'bparam'+index">Key {{index + 1}}</label>
            <input :name="'bparam'+index" v-model="param.key">
          </li>
          <li>
            <label :for="'bvalue'+index">Value {{index + 1}}</label>
            <input :name="'bvalue'+index" v-model="param.value">
          </li>
          <li>
            <label for="request">&nbsp;</label>
            <button name="request" @click="removeRequestBodyParam(index)">Remove</button>
          </li>
        </ol>
        <ul>
          <li>
            <label for="addrequest">Action</label>
            <button name="addrequest" @click="addRequestBodyParam">Add</button>
          </li>
        </ul>
        <ul>
          <li>
            <label for="request">Parameter List</label>
            <textarea name="request" rows="1" readonly>{{rawRequestBody || '(add at least one parameter)'}}</textarea>
          </li>
        </ul>
      </div>
      <div v-else>
        <textarea v-model="rawParams" style="font-family: monospace;" rows="16" @keydown="formatRawParams"></textarea>
      </div>
    </pw-section>
    <pw-section class="green" label="Authentication" collapsed>
      <ul>
        <li>
          <label for="auth">Authentication Type</label>
          <select v-model="auth">
            <option>None</option>
            <option>Basic</option>
            <option>Bearer Token</option>
          </select>
        </li>
      </ul>
      <ul v-if="auth === 'Basic'">
        <li>
          <label for="http_basic_user">User</label>
          <input v-model="httpUser">
        </li>
        <li>
          <label for="http_basic_passwd">Password</label>
          <input v-model="httpPassword" type="password">
        </li>
      </ul>
      <ul v-if="auth === 'Bearer Token'">
        <li>
          <label for="bearer_token">Token</label>
          <input v-model="bearerToken">
        </li>
      </ul>
    </pw-section>
    <pw-section class="orange" label="Headers" collapsed>
      <ol v-for="(header, index) in headers">
        <li>
          <label :for="'header'+index">Key {{index + 1}}</label>
          <input :name="'header'+index" v-model="header.key">
        </li>
        <li>
          <label :for="'value'+index">Value {{index + 1}}</label>
          <input :name="'value'+index" v-model="header.value">
        </li>
        <li>
          <label for="header">&nbsp;</label>
          <button name="header" @click="removeRequestHeader(index)">Remove</button>
        </li>
      </ol>
      <ul>
        <li>
          <label for="add">Action</label>
          <button name="add" @click="addRequestHeader">Add</button>
        </li>
      </ul>
      <ul>
        <li>
          <label for="request">Header List</label>
          <textarea name="request" rows="1" readonly>{{headerString || '(add at least one header)'}}</textarea>
        </li>
      </ul>
    </pw-section>
    <pw-section class="cyan" label="Parameters" collapsed>
      <ol v-for="(param, index) in params">
        <li>
          <label :for="'param'+index">Key {{index + 1}}</label>
          <input :name="'param'+index" v-model="param.key">
        </li>
        <li>
          <label :for="'value'+index">Value {{index + 1}}</label>
          <input :name="'value'+index" v-model="param.value">
        </li>
        <li>
          <label for="param">&nbsp;</label>
          <button name="param" @click="removeRequestParam(index)">Remove</button>
        </li>
      </ol>
      <ul>
        <li>
          <label for="add">Action</label>
          <button name="add" @click="addRequestParam">Add</button>
        </li>
      </ul>
      <ul>
        <li>
          <label for="request">Parameter List</label>
          <textarea name="request" rows="1" readonly>{{queryString || '(add at least one parameter)'}}</textarea>
        </li>
      </ul>
    </pw-section>
    <pw-section class="purple" label="Response" id="response" ref="response">
      <ul>
        <li>
          <label for="status">status</label>
          <input name="status" type="text" readonly :value="response.status || '(waiting to send request)'" :class="statusCategory ? statusCategory.className : ''">
        </li>
      </ul>
      <ul v-for="(value, key) in response.headers">
        <li>
          <label for="value">{{key}}</label>
          <input name="value" :value="value" readonly>
        </li>
      </ul>
      <ul>
        <li>
          <div class="flex-wrap">
            <label for="body">response</label>
            <button v-if="response.body" name="action" @click="copyResponse">Copy Response</button>
          </div>
          <textarea name="body" rows="16" id="response-details" readonly>{{response.body || '(waiting to send request)'}}</textarea>
        </li>
      </ul>
    </pw-section>
    <pw-section class="gray" label="History">
      <ul>
        <li>
          <button v-bind:class="{ disabled: noHistoryToClear }" v-on:click="clearHistory">Clear History</button>
        </li>
      </ul>
      <ul v-for="entry in history">
        <li>
          <label for="time">Time</label>
          <input name="time" type="text" readonly :value="entry.time">
        </li>
        <li class="method-list-item">
          <label for="method">Method</label>
          <input name="method" type="text" readonly :value="entry.method" :class="findEntryStatus(entry).className" :style="{'--status-code': entry.status}">
          <span class="entry-status-code">{{entry.status}}</span>
        </li>
        <li>
          <label for="url">URL</label>
          <input name="url" type="text" readonly :value="entry.url">
        </li>
        <li>
          <label for="path">Path</label>
          <input name="path" type="text" readonly :value="entry.path">
        </li>
        <li>
          <label for="delete">&nbsp;</label>
          <button name="delete" @click="deleteHistory(entry)">Delete</button>
        </li>
        <li>
          <label for="use">&nbsp;</label>
          <button name="use" @click="useHistory(entry)">Use</button>
        </li>
      </ul>
    </pw-section>
  </div>
</template>
<script>
  const statusCategories = [{
      name: 'informational',
      statusCodeRegex: new RegExp(/[1][0-9]+/),
      className: 'info-response'
    },
    {
      name: 'successful',
      statusCodeRegex: new RegExp(/[2][0-9]+/),
      className: 'success-response'
    },
    {
      name: 'redirection',
      statusCodeRegex: new RegExp(/[3][0-9]+/),
      className: 'redir-response'
    },
    {
      name: 'client error',
      statusCodeRegex: new RegExp(/[4][0-9]+/),
      className: 'cl-error-response'
    },
    {
      name: 'server error',
      statusCodeRegex: new RegExp(/[5][0-9]+/),
      className: 'sv-error-response'
    },
  ];
  const parseHeaders = xhr => {
    const headers = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
    const headerMap = {};
    headers.forEach(line => {
      const parts = line.split(': ');
      const header = parts.shift().toLowerCase();
      const value = parts.join(': ');
      headerMap[header] = value
    });
    return headerMap
  };
  const findStatusGroup = responseStatus => statusCategories.find(status => status.statusCodeRegex.test(responseStatus));
  import section from "../components/section";
  export default {
    components: {
      'pw-section': section
    },
    data() {
      return {
        method: 'GET',
        url: 'https://reqres.in',
        auth: 'None',
        path: '/api/users',
        httpUser: '',
        httpPassword: '',
        bearerToken: '',
        headers: [],
        params: [],
        bodyParams: [],
        rawParams: '',
        rawInput: false,
        contentType: 'application/json',
        response: {
          status: '',
          headers: '',
          body: ''
        },
        history: window.localStorage.getItem('history') ? JSON.parse(window.localStorage.getItem('history')) : []
      }
    },
    computed: {
      statusCategory() {
        return findStatusGroup(this.response.status);
      },
      noHistoryToClear() {
        return this.history.length === 0;
      },
      isValidURL() {
        const protocol = '^(https?:\\/\\/)?';
        const validIP = new RegExp(protocol + "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
        const validHostname = new RegExp(protocol + "(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$");
        return validIP.test(this.url) || validHostname.test(this.url);
      },
      rawRequestBody() {
        const {
          bodyParams
        } = this
        if (this.contentType === 'application/json') {
          try {
            const obj = JSON.parse(`{${bodyParams.filter(({ key }) => !!key).map(({ key, value }) => `
              "${key}": "${value}"
              `).join()}}`)
            return JSON.stringify(obj)
          } catch (ex) {
            return 'invalid'
          }
        } else {
          return bodyParams
            .filter(({
              key
            }) => !!key)
            .map(({
              key,
              value
            }) => `${key}=${encodeURIComponent(value)}`).join('&')
        }
      },
      headerString() {
        const result = this.headers
          .filter(({
            key
          }) => !!key)
          .map(({
            key,
            value
          }) => `${key}: ${value}`).join(',\n')
        return result == '' ? '' : `${result}`
      },
      queryString() {
        const result = this.params
          .filter(({
            key
          }) => !!key)
          .map(({
            key,
            value
          }) => `${key}=${encodeURIComponent(value)}`).join('&')
        return result == '' ? '' : `?${result}`
      }
    },
    methods: {
      findEntryStatus(entry) {
        return findStatusGroup(entry.status);
      },
      deleteHistory(entry) {
        this.history.splice(this.history.indexOf(entry), 1)
        window.localStorage.setItem('history', JSON.stringify(this.history))
      },
      clearHistory() {
        this.history = []
        window.localStorage.setItem('history', JSON.stringify(this.history))
      },
      useHistory({
        method,
        url,
        path
      }) {
        this.method = method
        this.url = url
        this.path = path
        this.$refs.request.$el.scrollIntoView({
          behavior: 'smooth'
        })
      },
      sendRequest() {
        if (this.$refs.response.$el.classList.contains('hidden')) {
          this.$refs.response.$el.classList.toggle('hidden')
        }
        this.$refs.response.$el.scrollIntoView({
          behavior: 'smooth'
        })
        this.response.status = 'Fetching...'
        this.response.body = 'Loading...'
        const xhr = new XMLHttpRequest()
        const user = this.auth === 'Basic' ? this.httpUser : null
        const pswd = this.auth === 'Basic' ? this.httpPassword : null
        xhr.open(this.method, this.url + this.path + this.queryString, true, user, pswd)
        if (this.auth === 'Bearer Token') {
          xhr.setRequestHeader('Authorization', 'Bearer ' + this.bearerToken);
        }
        if (this.headers) {
          this.headers.forEach(function(element) {
            xhr.setRequestHeader(element.key, element.value)
          })
        }
        if (this.method === 'POST' || this.method === 'PUT') {
          const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
          xhr.setRequestHeader('Content-Length', requestBody.length)
          xhr.setRequestHeader('Content-Type', `${this.contentType}; charset=utf-8`)
          xhr.send(requestBody)
        } else {
          xhr.send()
        }
        xhr.onload = e => {
          this.response.status = xhr.status
          const headers = this.response.headers = parseHeaders(xhr)
          if ((headers['content-type'] || '').startsWith('application/json')) {
            this.response.body = JSON.stringify(JSON.parse(xhr.responseText), null, 2)
          } else {
            this.response.body = xhr.responseText
          }
          if (!this.isValidURL) {
            alert('Please check the formatting of the URL');
            return
          }
          const n = new Date().toLocaleTimeString()
          this.history = [{
            status: xhr.status,
            time: n,
            method: this.method,
            url: this.url,
            path: this.path
          }, ...this.history]
          window.localStorage.setItem('history', JSON.stringify(this.history))
        }
        xhr.onerror = e => {
          this.response.status = xhr.status
          this.response.body = xhr.statusText
        }
      },
      addRequestHeader() {
        this.headers.push({
          key: '',
          value: ''
        })
        return false
      },
      removeRequestHeader(index) {
        this.headers.splice(index, 1)
      },
      addRequestParam() {
        this.params.push({
          key: '',
          value: ''
        })
        return false
      },
      removeRequestParam(index) {
        this.params.splice(index, 1)
      },
      addRequestBodyParam() {
        this.bodyParams.push({
          key: '',
          value: ''
        })
        return false
      },
      removeRequestBodyParam(index) {
        this.bodyParams.splice(index, 1)
      },
      formatRawParams(event) {
        if ((event.which !== 13 && event.which !== 9)) {
          return;
        }
        const textBody = event.target.value;
        const textBeforeCursor = textBody.substring(0, event.target.selectionStart);
        const textAfterCursor = textBody.substring(event.target.selectionEnd);
        if (event.which === 13) {
          event.preventDefault();
          const oldSelectionStart = event.target.selectionStart;
          const lastLine = textBeforeCursor.split('\n').slice(-1)[0];
          const rightPadding = lastLine.match(/([\s\t]*).*/)[1] || "";
          event.target.value = textBeforeCursor + '\n' + rightPadding + textAfterCursor;
          setTimeout(() => event.target.selectionStart = event.target.selectionEnd = oldSelectionStart + rightPadding.length + 1, 1);
        } else if (event.which === 9) {
          event.preventDefault();
          const oldSelectionStart = event.target.selectionStart;
          event.target.value = textBeforeCursor + '\xa0\xa0' + textAfterCursor;
          event.target.selectionStart = event.target.selectionEnd = oldSelectionStart + 2;
          return false;
        }
      },
      copyResponse() {
        var copyText = document.getElementById("response-details");
        copyText.select();
        document.execCommand("copy");
      }
    }
  }

</script>
